"""
Copyright 2012 Wieser Dietmar, Margit Mutschlechner.

This file is part of Multimedia Social Networking Platform.

Multimedia Social Networking Platform is free software: 
you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Multimedia Social Networking Platform is distributed
in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Multimedia Social Networking Platform.  
If not, see <http://www.gnu.org/licenses/>.

"""

from msnp.service.synch import SynchChange
from msnp.logger import Logger
from msnp.service import network, utils
from msnp.model import PTPModel, MediaModel
from msnp.service.network import logic, sending
from msnp.service.network.model import Model
from msnp.persistence import XMLController, PersistenceController
from msnp.model.MediaModel import NetSynchChanges, SynchContent, MediaReceiverQueueEntry
from msnp.service.network import NetworkLock
from msnp.service.utils import StringCompare
from msnp.service.network import NetworkController

"""This module contains all functions needed for the synchronization with other peers over the network:
- receiving media content (update the own media database and log changes)
- push media content (if own media db changed content gets pushed to all other members)
- poll media content since last synch (if I was offline I ask all other members of the groups I am member)
- various helper functions."""

def receiveMediaContent(content, timestamp):
    """This function gets called when the peer receives media content from another peer.
    
    If synchIn is set for the group the content is processed in the following way:
    the corresponding entries in the media database get updated
    and all changes are logged in the database.
    If synchIn is not set the content is not processed but gets logged anyway
    to know later on that the peer missed an update.
    
    Args:
        content: A BackupContent object containing the whole backup.
        timestamp: The exact time when the backup was sent.
    """
    for synchContent in content:
        groupname = synchContent.getGroupname()
        ownername = synchContent.getOwnername()
        username = synchContent.getSenderName()
        Logger.info("new media synch content received from " + username)
        settings = XMLController.readAllSettings()
        synchIn = settings.getCurrentUserSettings().isSynchOnForGroup(groupname, ownername, False)
    #    timestamp = utils.DateFormatter.formatTimeAsFloat()
        if synchIn:
            netSynchChange = NetSynchChanges(None, groupname, ownername, username, timestamp, True, MediaModel.NetSynchChangeSource.NETWORK)
            synchChange = SynchChange.SynchChangesWriter()
            Logger.info(str(len(synchContent.getAudioList())) + " audios received")
            for audio in synchContent.getAudioList():
                Logger.info(" - audio '" + audio.getMedia().getTitle() + "'")
                synchChange.processChange(audio)
            changedMediaIds = synchChange.getChangedMediaIds()
            # log processed change coming from the network
            if changedMediaIds != None and len(changedMediaIds) > 0:
                for changedMediaId in changedMediaIds:
                    netSynchChange.setMediaId(changedMediaId)
                    PersistenceController.insertNetSynchChange(netSynchChange)
        else:
            Logger.info("no synchIn for group " + groupname + " (" + ownername + ")")
            # log not processed update coming from the network
            netSynchChange = NetSynchChanges(None, groupname, ownername, username, timestamp, False, MediaModel.NetSynchChangeSource.NETWORK)
            PersistenceController.insertNetSynchChange(netSynchChange)
        
def pollMediaContentSinceLastSynch(groups):
    """This function is called when a peer has to poll media content from other peers
    because it did not synchronize before (eg. offline).
    
    For all given groups it either searches its members (getGroups) or, if they are already given,
    it calls the pollMediaContentSinceLastSynch at the peer (identities of the peers are not given,
    only their name, therefore the call gets routed through the ring).
    
    Args:
        groups: A list of Group objects containing the groups from which's user the media content should be polled. 
    """
    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
    
    for group in groups:
        groupname = group.getGroupname()
        ownername = group.getOwnername()
        timestamp = PersistenceController.selectLastUpdateProcessedTimestamp(groupname, ownername)
        if timestamp > -1:
            groupInDb = PersistenceController.selectGroupForGroupnameAndOwnername(groupname, ownername)
            if doSynchWithGroup(groupInDb, False):
                if groupInDb != None and groupInDb.hasDetails():
                    for member in groupInDb.getMembersByStatus(PTPModel.MemberStatus.ACTIVE):
                        if member.getName() != myName:
                            if network.NetworkController.getMySelf().isLowLevelPeer():
                                respIdentity = network.NetworkController.getMySelf().getResponsibleSuperPeer()
                            else:
                                respIdentity = network.NetworkController.getMySelf().getMyIdentity()
                            Logger.info("poll media content from group " + groupname + " (" + ownername + ") from member " + member.getName())
                            sending.Sender.pollMediaContentSinceLastSynch(respIdentity, myName, member.getName(), groupInDb, timestamp)
                        else:
                            Logger.info("don't poll media content from myself")
                else:
                    Logger.info("have to search group " + groupname + " (" + ownername + ")")
                    network.NetworkController.getGroups(group.getOwnername(), group.getGroupname(), None, None, Model.CallbackReactType.SEARCHMEMBER_INITSYNCH)
        else:
            Logger.info("no media content to retrieve from group " + groupname + " (" + ownername + "): no last update timestamp")
            
def getMediaContentToSendSinceLastSynch(senderName, group, timestamp):
    """This function collects all medias that have to be send to the user.
    
    Collects medias that were changed since the given timestamp and
    that correspond to at least one genre of the group.    
    It retrieves the media ids of all changed medias, selects the corresponding medias
    and checks for the genre. It returns the matching medias wrapped in a SynchContent object.
    
    Args:
        senderName: A String containing the name of the user that receives the media content.
        group: A Group object containing the the group where the sender is a member of.
            Needed to send only entries that correspond with the genre of the group.
        timestamp: The exact time when the last media content was sent to the receiving peer.
        
    Returns:
        A SynchContent object containing all media objects to be send to the user is returned,
        or None if there is no such content.
    """
    
    groupname = group.getGroupname()
    ownername = group.getOwnername()
    Logger.info("try to send media content to " + senderName + " for group " + groupname + " (" + ownername + ") since " + str(timestamp))
    mediaIds = PersistenceController.selectNetSynchMediaIdsProcessedSince(timestamp)
    audioList = PersistenceController.selectAudiosForMediaIds(mediaIds)
    if audioList != None and len(audioList) > 0:
        audiosToSend = []
        for audio in audioList:
            audioGenre = audio.getMedia().getGenre()
            if audioGenre != None:
                if group.containsGenre(audioGenre.getName()):
                    audiosToSend.append(audio)
                    Logger.info(" - send " + audio.getMedia().getTitle())
                else:
                    Logger.info(" - don't send " + audio.getMedia().getTitle() + ", not matching genre")
            else:
                Logger.info(" - don't send " + audio.getMedia().getTitle() + ", no genre for audio")
        if len(audiosToSend) > 0:
            Logger.info(str(len(audiosToSend)) + " audios found")
            myName = network.NetworkController.getMySelf().getMyIdentity().getName()
            return SynchContent(audiosToSend, groupname, ownername, myName)
        else:
            Logger.info("no audios matching with genre")
    else:
        Logger.info("no audios processed since")
    return None
        
def pushMediaContent():
    """This function is called if the content of the media database changed and has to be pushed to other peers.
    
    For every group the peer is a member of it either searches its members (getGroup)
    or, if the members are already given, their identity gets searched
    with the findResponsibleIdentity function (when the identity is found the media content gets send).
    """
    
    groupsIAmMember = NetworkController.getGroupsIAmMember()
    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
    if groupsIAmMember != None:
        for group in groupsIAmMember:
            if doSynchWithGroup(group, True):
                if group != None and group.hasDetails():
                    myIdentity = NetworkController.getMySelf().getMyIdentity()
                    for member in group.getMembersByStatus(PTPModel.MemberStatus.ACTIVE):
                        if member.getName() != myName:
                            addGroupForUsername(member.getName(), group, member.getTimestamp())
                            if network.NetworkController.getMySelf().isLowLevelPeer():
                                respIdentity = network.NetworkController.getMySelf().getResponsibleSuperPeer()
                                sending.Sender.forwardResponsibilityCall(member.getName(), myIdentity.getName(), respIdentity, Model.CallbackReactType.SEARCHMEMBER_SENDMEDIA)
                            else:
                                logic.ResponsibilityLogic.findResponsibleIdentity(member.getName(), myIdentity.getName(), myIdentity, Model.CallbackReactType.SEARCHMEMBER_SENDMEDIA)
                # search for group details, search for members at callback
                else:
                    network.NetworkController.getGroups(group.getOwnername(), group.getGroupname(), None, None, Model.CallbackReactType.SEARCHMEMBER_SENDMEDIA)
    else:
        Logger.info("I am not a member of a group, so don't send media content")

def pushMediaContentToMember(receiverName, receiverRespIdentity):
    """This function is called when the identity of the member, that should receive media content, was found.
    
    It retrieves the media content that has to be pushed
    and, if it is not None, sends it to the user.
    
    Args:
        receiverName: The user name of the peer that receives the media content.
        receiverRespIdentity: The identity of the responsible Super Peer of the receiving peer.    
    """
    content = __getContentToSend(receiverName)
    if content != None:
        timestamp = utils.DateFormatter.formatTimeAsFloat()
        sending.Sender.sendMediaContent(content, timestamp, receiverRespIdentity, receiverName)
    else:
        Logger.info("no media content to push to member " + receiverName)


def doSynchWithGroup(group, isOutSynch):
    """Checks whether the user wants to synchronize with the group or not.
    
    Reads the settings and checks for the synchronization settings regarding the given group.
    If isOutSynch is true it checks if the user wants to send synchronization content to the group,
    otherwise it checks if the user wants to receive synchronization content from the group.
    
    Args:
        group: The Group object for which the synchronization settings have to be checked.
        isOutSynch: A boolean value, true if the synchronization is outwards,
            false if its a incoming synchronization.
            
    Returns:
        True if the synchronization settings are set to true for the given group, otherwise false.
    """
    settings = XMLController.readAllSettings()
    if group != None:
        if settings != None and settings.getCurrentUserSettings().isSynchOnForGroup(group.getGroupname(), group.getOwnername(), isOutSynch):
            if group.hasDetails():
                if group.isActive():
                    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
                    if group.getMember(myName) != None and group.getMember(myName).getStatus() == PTPModel.MemberStatus.ACTIVE:
                        members = group.getMembersByStatus(PTPModel.MemberStatus.ACTIVE)
                        if members != None and len(members) > 0:
                            Logger.info("synch with group " + group.getGroupname() + " (" + group.getOwnername() + ")")
                            return True
                        else:
                            Logger.info("no members in group " + group.getGroupname() + " (" + group.getOwnername() + ")")
                    else:
                        Logger.info("I am not an active member of group " + group.getGroupname() + " (" + group.getOwnername() + ")")
                else:
                    Logger.info("group " + group.getGroupname() + " (" + group.getOwnername() + ") is inactive, so don't send media content")
            else:
                Logger.info("group " + group.getGroupname() + " (" + group.getOwnername() + ") has no details, don't know if I should synch")
                return True
        else:
            Logger.info("synch out is set to false for group " + group.getGroupname() + " (" + group.getOwnername() + "), so don't send media content")
    else:
        Logger.info("group is None, don't know if I should synch")
        return True
    return False

__userGroupMap = {} #for example: __userGroupMap["user1"] = [group2, group3]

def addGroupForUsername(username, group, timestamp):
    """Adds a username - MediaReceiverQueueEntry mapping to the dictionary.
    
    For every member that has to receive media content its groups are stored
    so that when sending media content the corresponding audio files can be selected
    (genres have to coincide).
    The mapping contains the username and a MediaReceiverQueueEntry object
    consisting of the group and the timestamp.
    
    Args:
        username: The name of the user.
        group: The Group object the user has to be mapped to.
        timestamp: The exact timestamp when the mapping is created.
    """
    global __userGroupMap
    
    NetworkLock.acquireSendMediaLock()
    try:
        entry = MediaReceiverQueueEntry(group, timestamp)
        if __userGroupMap.get(username) != None and len(__userGroupMap.get(username)) > 0:
            __userGroupMap.get(username).append(entry)
        else:
            __userGroupMap[username] = [entry]
    finally:
        NetworkLock.releaseSendMediaLock()

def __getContentToSend(username):
    """For a given username it retrieves all corresponding audio files that have to be pushed to that peer.
    
    First it retrieves from the user-group mapping all groups the user is a member of.
    Then it calculates for every group the last media id the user synchronized with it, which is one of the following:
    - if I already synchronized with that peer I have an entry in the netsynchchanges table
    - otherwise take the higher of the following to media ids:
      - the media id in the netsynchtable that is nearest to the timestamp of my join
      - the media id in the netsynchtable that is nearest to the timestamp of the joining of the other peer.
    Then it takes all medias that were changed from that time on.
    The medias that have the same genres as one of the groups are selected and returned.
    
    Args:
        username:
        
    Returns:
        It returns the retrieved content that has to be sent to the user, or None if there is no such content.
    """
    
    global __userGroupMap
    
    NetworkLock.acquireSendMediaLock()
    try:
        Logger.info("get audios to push to " + username)
        entries = __userGroupMap.get(username)
        if entries != None and len(entries) > 0:
            myName = network.NetworkController.getMySelf().getMyIdentity().getName()
            content = []
            for entry in entries:
                groupname = entry.getGroup().getGroupname()
                ownername = entry.getGroup().getOwnername()
                genres = entry.getGroup().getGenres()
                timestamp = entry.getTimestamp()
                
                Logger.info("- group " + groupname + " (" + ownername + ")")
                
                # if I already synchronized once I stored the last netsynch id
                oldLastNetSynchId = PersistenceController.selectLastNetSynchIdForUserAndGroup(username, groupname, ownername)
                if oldLastNetSynchId == -1:
                    # otherwise take the greater of the following two ids:
                    # - id that is nearest to the join time of the user
                    # - id that is nearest to my join time
                    myNearestId = PersistenceController.selectNetSynchIdForGroupInitial(groupname, ownername)
                    nearestId = PersistenceController.selectNearestNetSynchIdForTimestamp(timestamp) - 1
                    if myNearestId > nearestId:
                        oldLastNetSynchId = myNearestId
                        Logger.info("  never synchronized, take everything since my join: " + str(oldLastNetSynchId))
                    else:
                        oldLastNetSynchId = nearestId
                        Logger.info("  never synchronized, take everything since join of other peer (" + str(timestamp) + " ): " + str(oldLastNetSynchId))
                else:
                    Logger.info("  synchronized last time until id: " + str(oldLastNetSynchId))
                # store highest netsynch id as last net synch id for this user 
                newLastNetSynchId = PersistenceController.selectLastNetSynchId()
                Logger.info("   new lastnetsynch id is " + str(newLastNetSynchId))
                if newLastNetSynchId > -1:
                    PersistenceController.setLastNetSynchIdForUserAndGroup(username, groupname, ownername, newLastNetSynchId)
                # retrieve all relevant audios that got changed by the player
                changedMedias = PersistenceController.selectNetSynchMediasFromPlayerSince(oldLastNetSynchId)
                if changedMedias != None and len(changedMedias) > 0:
                    mediaIds = []
                    for change in changedMedias:
                        mediaIds.append(change.getMediaId())
                    changedAudios = PersistenceController.selectAudiosForMediaIds(mediaIds, myName)
                    
                    if changedAudios != None and len(changedAudios) > 0:
                        audiosToSynch = []
                        # search for audios that have the same genre as the group
                        if genres != None:
                            for genre in genres:
                                for changedAudio in changedAudios:
                                    collectionName = changedAudio.getMedia().getCollection().getName() if changedAudio.getMedia().getCollection() != None else ""
                                    artistName = changedAudio.getArtist().getName() if changedAudio.getArtist() != None else ""
                                    # audio has player genre, group has our predefined genres -> compare them
                                    if StringCompare.isEquals(changedAudio.getMedia().getGenre().getName(), genre.getName()) and audiosToSynch.__contains__(changedAudio) == False:
                                        audiosToSynch.append(changedAudio)
                                        Logger.info("  - SEND: " + changedAudio.getMedia().getTitle() + " from " + artistName + " in " + collectionName)
                                    else:
                                        Logger.info("  - DON'T SEND: " + changedAudio.getMedia().getTitle() + " from " + artistName + " in " + collectionName)
                        if audiosToSynch != None and len(audiosToSynch) > 0:
                            c = SynchContent(audiosToSynch, groupname, ownername, myName)
                            content.append(c)
                        else:
                            Logger.info("audios changed, but no audios to send")
                    else:
                        Logger.info("error!! medias changed but got no audios??")
                else:
                    Logger.info("no audios changed")
            __userGroupMap[username] = None
            return content
        Logger.info("nothing to send, no group stored")
        return None
    finally:
        NetworkLock.releaseSendMediaLock()
